//
// Copyright (c) 2010 Luís Reis
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
using System;
using System.IO;
using Pine.Core;

namespace Pine.IO
{
	public class IOStream : ClassInstance, IDisposable
	{
		private Stream stream;
		public static readonly string Read = "r";
		public static readonly string ReadOrCreate = "r+";
		public static readonly string Append = "a";
		public static readonly string Write = "w";
		
		public static readonly PineType IOType = new DynamicType(null, CreateFromList, null, null);
		
		private bool isDisposed;
		
		public IOStream (Stream stream)
		{
			SetupFields();
			this.stream = stream;
		}
		
		public IOStream (string filename, string mode)
		{
			SetupFields();
			CreateStream(filename, mode);
		}
		
		~IOStream() {
			Dispose(false);
		}
		
		public void Dispose() {
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		
		private void Dispose(bool disposing) {
			if (!this.isDisposed) {
				stream.Dispose();
			}
			isDisposed = true;
		}
		
		private void SetupFields() {
			ReadWrapper = new Function(null, delegate(Function f, ValueList rl) {
				rl.Clear();
				rl.SetArrayElement(new Number(0), ReadFunction());
			});
			WriteWrapper = new Function(null, delegate(Function f, ValueList rl) {
				Value v = rl.GetArrayElement(0);
				rl.Clear();
				rl.SetArrayElement(new StringValue("err"), WriteFunction(v));	
			});
			CloseWrapper = new Function(null, delegate(Function f, ValueList rl) {
				Close();
			});
		}
		
		private void CreateStream(string filename, string mode) {
			if (mode == Read) {
				stream = new FileStream(filename, FileMode.Open, FileAccess.Read);	
			} else if (mode == ReadOrCreate) {
				stream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Read);	
			} else if (mode == Append) {
				stream = new FileStream(filename, FileMode.Append, FileAccess.Write);	
			} else if (mode == Write) {
				stream = new FileStream(filename, FileMode.Truncate, FileAccess.Write);
			} else {
				throw new PineException("Unrecognized file open mode");	
			}	
		}
		
		public static void CreateFromList (DynamicType type, ValueList rl) {
			if (rl == null) {
				throw new ArgumentNullException("rl", "Right list is null (not NullValue)");
			}
			Value v0 = rl.GetArrayElement(0);
			Value v1 = rl.GetArrayElement(1);
			
			rl.Clear();
			
			if (v0 == NullValue.Null) {
				rl.SetArrayElement(new Number(0), v0);
				return;
			} else if (v0.GetPineType() != PineType.StringType) {
				rl.SetArrayElement(new StringValue("err"), new StringValue("Invalid argument 0. Expecting filename."));
				return;
			}
			
			if (v1 == NullValue.Null) {
				v1 = new StringValue(ReadOrCreate);
				rl.SetArrayElement(new StringValue("info"), new StringValue("guess-read-mode"));
			} else if (v1.GetPineType() != PineType.StringType) {
				rl.SetArrayElement(new StringValue("err"), new StringValue("Invalid argument 1. Expecting read mode."));
				return;
			}
			
			string filename = v0.ToString();
			string readmode = v1.ToString();
			
			rl.SetArrayElement(new Number(0), new IOStream(filename, readmode));
		}
		
		public Value ReadFunction() {
			return new Number((double) stream.ReadByte());
		}
		
		public Value WriteFunction(Value v) {
			Number nv = v as Number;
			if (nv != null) {
				stream.WriteByte((byte) nv.Value);
				return NullValue.Null;
			}
			return new StringValue("Attempting to write non-number value");
		}
		
		public void Close() {
			stream.Close();
		}
		
		private Function ReadWrapper;
		private Function WriteWrapper;
		private Function CloseWrapper;
		
		public override Value GetPineType() {
			return IOType;
		}
		
		public override Value FieldGet (string fieldname)
		{
			if (fieldname == "read")
				return ReadWrapper;
			if (fieldname == "write")
				return WriteWrapper;
			if (fieldname == "close")
				return CloseWrapper;
			return base.FieldGet (fieldname);
		}
	}
}

